package com.evildog.workspace.gateway.fillter;

import com.evildog.workspace.common.result.Result;
import com.evildog.workspace.gateway.props.IgnoreUrlsProperties;
import com.evildog.workspace.gateway.provider.ResponseProvider;
import com.evildog.workspace.gateway.util.IgnoreUtil;
import com.evildog.workspace.security.jwt.util.SecurityUtil;
import com.evildog.workspace.system.api.feign.ApiPermissionClient;
import com.evildog.workspace.system.api.vo.ApiPermissionVO;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.PathMatcher;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * 鉴权
 */
@Slf4j
@Component
@RequiredArgsConstructor
@EnableConfigurationProperties({IgnoreUrlsProperties.class})
public class PermissionFilter implements GlobalFilter, Ordered {

    // 缓存权限数据
    private static final Cache<String, List<ApiPermissionVO>> permissionCache = Caffeine.newBuilder()
            // 最大数量
            .maximumSize(1024)
            // 弱引用key
            .weakKeys()
            // 弱引用value
            .weakValues()
            // 过期机制
            .expireAfterWrite(5, TimeUnit.MINUTES)
            .removalListener((key, value, cause) -> log.info("key:{}, value:{}, 删除原因:{}", key, value, cause))
            .build();

    public static final String KEY_ALL_API_PERMISSION = "all_api_permission";
    public static final String KEY_USER_API_PERMISSION = "user_api_permission?userId=$1%s";

    private final ObjectMapper objectMapper;
    private final IgnoreUrlsProperties ignoreUrlsProperties;
    private final ApiPermissionClient apiPermissionClient;

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpResponse resp = exchange.getResponse();

        String path = exchange.getRequest().getURI().getPath();
        if (IgnoreUtil.isSkip(path, ignoreUrlsProperties)) {
            return chain.filter(exchange);
        }

        String method = exchange.getRequest().getMethodValue();

        ApiPermissionVO currentApiPermission = new ApiPermissionVO().setPath(path)
                .setMethod(method);

        // 所有api权限
        // TODO 将路径需要的权限缓存
        Set<ApiPermissionVO> requireApiPermissions = getRequireApiPermissions(currentApiPermission);

        // 没有权限要求，直接放行
        // 根据需要最好修改为相反的策略,后台管理项目，自己开发图方便，这样做，但安全性不高
        if (requireApiPermissions.isEmpty()) {
            return chain.filter(exchange);
        }

        // 没有包含所有要求的权限，Forbidden
        if (!hasAllPermission(requireApiPermissions)) {
            return forbidden(resp);
        }

        return chain.filter(exchange);
    }

    private boolean hasAllPermission(Set<ApiPermissionVO> requireApiPermissions) {
        List<ApiPermissionVO> userPermissions = getUserApiPermissions();
        // 只需要用户具备其中一个permission，就算拥有权限.
        return userPermissions.stream().anyMatch(requireApiPermissions::contains);
    }

    /**
     * 获取用户包含的权限
     */
    private List<ApiPermissionVO> getUserApiPermissions() {
        Long userId = SecurityUtil.getUserId();
        String key = String.format(KEY_USER_API_PERMISSION, userId);
        return permissionCache.get(key, k -> {
            Result<List<ApiPermissionVO>> userPermissionsResult = apiPermissionClient.getUserPermissions(userId);
            if (userPermissionsResult.isSuccess()) {
                return userPermissionsResult.getData();
            } else {
                return null;
            }
        });
    }

    private List<ApiPermissionVO> getAllApiPermissions() {
        return permissionCache.get(KEY_ALL_API_PERMISSION, k -> {
            Result<List<ApiPermissionVO>> allPermissions = apiPermissionClient.getAllPermissions();
            if (allPermissions.isSuccess()) {
                return allPermissions.getData();
            } else {
                return null;
            }
        });
    }

    /**
     * @param currentApiPermission 当前路径的权限
     */
    private Set<ApiPermissionVO> getRequireApiPermissions(ApiPermissionVO currentApiPermission) {
        List<ApiPermissionVO> allPermissions = getAllApiPermissions();

        PathMatcher pathMatcher = new AntPathMatcher();
        return allPermissions.stream()
                .filter(permission ->
                        (
                                Objects.equals(permission.getMethod(), currentApiPermission.getMethod())
                                        || Objects.equals(currentApiPermission.getMethod(), ApiPermissionVO.ALLOW_ALL_METHOD)
                        )
                                && pathMatcher.match(permission.getPath(), currentApiPermission.getPath()))

                .collect(Collectors.toSet());
    }


    private Mono<Void> forbidden(ServerHttpResponse resp) {
        resp.setStatusCode(HttpStatus.FORBIDDEN);
        resp.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
        String result = "";
        try {
            result = objectMapper.writeValueAsString(ResponseProvider.forbidden());
        } catch (JsonProcessingException e) {
            log.error(e.getMessage(), e);
        }
        DataBuffer buffer = resp.bufferFactory().wrap(result.getBytes(StandardCharsets.UTF_8));
        return resp.writeWith(Flux.just(buffer));
    }

    @Override
    public int getOrder() {
        return Ordered.HIGHEST_PRECEDENCE + 100;
    }

}
